package com.snowleopard.common.base;

import java.io.Serializable;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.util.Assert;
import org.springside.modules.orm.Page;
import org.springside.modules.orm.hibernate.HibernateDao;

public class BaseDao<T extends BaseEntity, PK extends Serializable> extends HibernateDao<T, PK> {

    @Override
    public void save(final T entity) {
        entity.updateAuditInfo();
        super.save(entity);
    }

    public void update(final T entity) {
        entity.updateAuditInfo();
        this.getSession().update(entity);
    }

    public void merge(final T entity) {
        entity.updateAuditInfo();
        this.getSession().merge(entity);
    }

    public void persist(final T entity) {
        entity.updateAuditInfo();
        this.getSession().persist(entity);
    }

    @SuppressWarnings("unchecked")
    @Override
    public T get(final PK id) {
        return (T) getSession().get(entityClass, id);
    }

    @SuppressWarnings("unchecked")
    public T load(final PK id) {
        return (T) getSession().load(entityClass, id);
    }

    public static final int DESC = 1;
    public static final int ASC = 0;

    public void pagination(final int pageNo, final int pageList, final Query query) {
        if (pageList > 0 && pageNo > 0) {
            query.setMaxResults(pageList);
            int beginIndex = (pageNo - 1) * pageList;
            query.setFirstResult(beginIndex);
        }
    }

    public void pagination(final int pageNo, final int pageList, final Criteria criteria) {
        if (pageList > 0 && pageNo > 0) {
            criteria.setMaxResults(pageList);
            int beginIndex = (pageNo - 1) * pageList;
            criteria.setFirstResult(beginIndex);
        }
    }

    public void addOrder(final int order, final String sort, final Criteria criteria) {

        if (sort != null) {
            if (order == DESC) {
                criteria.addOrder(Order.desc(sort));
            } else {
                criteria.addOrder(Order.asc(sort));
            }
        }
    }

    public void le(final String propertyName, final Object value, final Criteria criteria) {
        if (value != null) {
            Criterion criterion = Restrictions.le(propertyName, value);
            criteria.add(criterion);
        }
    }

    public void lt(final String propertyName, final Object value, final Criteria criteria) {
        if (value != null) {
            Criterion criterion = Restrictions.lt(propertyName, value);
            criteria.add(criterion);
        }
    }

    public void gt(final String propertyName, final Object value, final Criteria criteria) {
        if (value != null) {
            Criterion criterion = Restrictions.gt(propertyName, value);
            criteria.add(criterion);
        }
    }

    public void ge(final String propertyName, final Object value, final Criteria criteria) {
        if (value != null) {
            Criterion criterion = Restrictions.ge(propertyName, value);
            criteria.add(criterion);
        }
    }

    public void eq(final String propertyName, final Boolean value, final Criteria criteria) {
        if (value != null) {
            Criterion criterion = Restrictions.eq(propertyName, value);
            criteria.add(criterion);
        }
    }

    public void eq(final String propertyName, final Enum<?> value, final Criteria criteria) {
        if (value != null) {
            Criterion criterion = Restrictions.eq(propertyName, value);
            criteria.add(criterion);
        }
    }

    public void eq(final String propertyName, final String value, final Criteria criteria) {
        if (StringUtils.isNotEmpty(value)) {
            Criterion criterion = Restrictions.eq(propertyName, value);
            criteria.add(criterion);
        }
    }

    public void eq(final String propertyName, final int value, final Criteria criteria) {
        if (value > 0) {
            Criterion criterion = Restrictions.eq(propertyName, value);
            criteria.add(criterion);
        }
    }

    public void eq(final String propertyName, final Integer value, final Criteria criteria) {
        if (value != null) {
            Criterion criterion = Restrictions.eq(propertyName, value);
            criteria.add(criterion);
        }
    }

    public void like(final String propertyName, final String value, final Criteria criteria) {
        if (StringUtils.isNotEmpty(value)) {
            Criterion criterion = Restrictions.like(propertyName, value, MatchMode.ANYWHERE);
            criteria.add(criterion);
        }
    }

    public void in(final String propertyName, final String[] value, final Criteria criteria) {
        if (value.length > 1) {
            Criterion criterion = Restrictions.in(propertyName, value);
            criteria.add(criterion);
        }
    }

    // list

    public void le(final String propertyName, final Object value, final List<Criterion> criterions) {
        if (value != null) {
            Criterion criterion = Restrictions.le(propertyName, value);
            criterions.add(criterion);
        }
    }

    public void lt(final String propertyName, final Object value, final List<Criterion> criterions) {
        if (value != null) {
            Criterion criterion = Restrictions.lt(propertyName, value);
            criterions.add(criterion);
        }
    }

    public void gt(final String propertyName, final Object value, final List<Criterion> criterions) {
        if (value != null) {
            Criterion criterion = Restrictions.gt(propertyName, value);
            criterions.add(criterion);
        }
    }

    public void ge(final String propertyName, final Object value, final List<Criterion> criterions) {
        if (value != null) {
            Criterion criterion = Restrictions.ge(propertyName, value);
            criterions.add(criterion);
        }
    }

    public void eq(final String propertyName, final Boolean value, final List<Criterion> criterions) {
        if (value != null) {
            Criterion criterion = Restrictions.eq(propertyName, value);
            criterions.add(criterion);
        }
    }

    public void eq(final String propertyName, final Enum<?> value, final List<Criterion> criterions) {
        if (value != null) {
            Criterion criterion = Restrictions.eq(propertyName, value);
            criterions.add(criterion);
        }
    }

    public void eq(final String propertyName, final String value, final List<Criterion> criterions) {
        if (StringUtils.isNotEmpty(value)) {
            Criterion criterion = Restrictions.eq(propertyName, value);
            criterions.add(criterion);
        }
    }

    public void eq(final String propertyName, final int value, final List<Criterion> criterions) {
        if (value > 0) {
            Criterion criterion = Restrictions.eq(propertyName, value);
            criterions.add(criterion);
        }
    }

    public void eq(final String propertyName, final Integer value, final List<Criterion> criterions) {
        if (value != null) {
            Criterion criterion = Restrictions.eq(propertyName, value);
            criterions.add(criterion);
        }
    }

    public void like(final String propertyName, final String value, final List<Criterion> criterions) {
        if (StringUtils.isNotEmpty(value)) {
            Criterion criterion = Restrictions.like(propertyName, value, MatchMode.ANYWHERE);
            criterions.add(criterion);
        }
    }

    public void in(final String propertyName, final String[] value, final List<Criterion> criterions) {
        if (value.length > 1) {
            Criterion criterion = Restrictions.in(propertyName, value);
            criterions.add(criterion);
        }
    }

    //
    private static final ThreadLocal<Criteria> criteriaThread = new ThreadLocal<Criteria>();

    private Criteria getCriteria() {
        Criteria criteria = criteriaThread.get();
        if (criteria == null) {
            criteria = createCriteria();
            criteriaThread.set(criteria);
        }
        return criteria;
    }

    public BaseDao<T, PK> isNull(final String propertyName) {
        Criteria criteria = getCriteria();
        Criterion criterion = Restrictions.isNull(propertyName);
        criteria.add(criterion);
        return this;
    }

    public BaseDao<T, PK> isNotNull(final String propertyName) {
        Criteria criteria = getCriteria();
        Criterion criterion = Restrictions.isNotNull(propertyName);
        criteria.add(criterion);
        return this;
    }

    public BaseDao<T, PK> isEmpty(final String propertyName) {
        Criteria criteria = getCriteria();
        Criterion criterion = Restrictions.isEmpty(propertyName);
        criteria.add(criterion);
        return this;
    }

    public BaseDao<T, PK> isNotEmpty(final String propertyName) {
        Criteria criteria = getCriteria();
        Criterion criterion = Restrictions.isNotEmpty(propertyName);
        criteria.add(criterion);
        return this;
    }

    public BaseDao<T, PK> pagination(final int pageNo, final int pageList) {
        Criteria criteria = getCriteria();
        if (pageList > 0 && pageNo > 0) {
            criteria.setMaxResults(pageList);
            int beginIndex = (pageNo - 1) * pageList;
            criteria.setFirstResult(beginIndex);
        }
        return this;
    }

    public BaseDao<T, PK> addOrder(final int order, final String sort) {
        Criteria criteria = getCriteria();

        if (sort != null) {
            if (order == DESC) {
                criteria.addOrder(Order.desc(sort));
            } else {
                criteria.addOrder(Order.asc(sort));
            }
        }
        return this;
    }

    public BaseDao<T, PK> le(final String propertyName, final Object value) {
        Criteria criteria = getCriteria();
        if (value != null) {
            Criterion criterion = Restrictions.le(propertyName, value);
            criteria.add(criterion);
        }
        return this;
    }

    public BaseDao<T, PK> lt(final String propertyName, final Object value) {
        Criteria criteria = getCriteria();
        if (value != null) {
            Criterion criterion = Restrictions.lt(propertyName, value);
            criteria.add(criterion);
        }
        return this;
    }

    public BaseDao<T, PK> gt(final String propertyName, final Object value) {
        Criteria criteria = getCriteria();
        if (value != null) {
            Criterion criterion = Restrictions.gt(propertyName, value);
            criteria.add(criterion);
        }
        return this;
    }

    public BaseDao<T, PK> ge(final String propertyName, final Object value) {
        Criteria criteria = getCriteria();
        if (value != null) {
            Criterion criterion = Restrictions.ge(propertyName, value);
            criteria.add(criterion);
        }
        return this;
    }

    public BaseDao<T, PK> eq(final String propertyName, final Boolean value) {
        Criteria criteria = getCriteria();
        if (value != null) {
            Criterion criterion = Restrictions.eq(propertyName, value);
            criteria.add(criterion);
        }
        return this;
    }

    public BaseDao<T, PK> eq(final String propertyName, final BaseEntity entity) {
        Criteria criteria = getCriteria();
        if (entity != null) {
            Criterion criterion = Restrictions.eq(propertyName, entity);
            criteria.add(criterion);
        }
        return this;
    }

    public BaseDao<T, PK> eq(final String propertyName, final Enum<?> value) {
        Criteria criteria = getCriteria();
        if (value != null) {
            Criterion criterion = Restrictions.eq(propertyName, value);
            criteria.add(criterion);
        }
        return this;
    }

    public BaseDao<T, PK> eq(final String propertyName, final String value) {
        Criteria criteria = getCriteria();
        if (StringUtils.isNotEmpty(value)) {
            Criterion criterion = Restrictions.eq(propertyName, value);
            criteria.add(criterion);
        }
        return this;
    }

    public BaseDao<T, PK> eq(final String propertyName, final int value) {
        Criteria criteria = getCriteria();
        if (value > 0) {
            Criterion criterion = Restrictions.eq(propertyName, value);
            criteria.add(criterion);
        }
        return this;
    }

    public BaseDao<T, PK> eq(final String propertyName, final Integer value) {
        Criteria criteria = getCriteria();
        if (value != null) {
            Criterion criterion = Restrictions.eq(propertyName, value);
            criteria.add(criterion);
        }
        return this;
    }

    public BaseDao<T, PK> ne(final String propertyName, final Object value) {
        Criteria criteria = getCriteria();
        if (value != null) {
            Criterion criterion = Restrictions.ne(propertyName, value);
            criteria.add(criterion);
        }
        return this;
    }

    public BaseDao<T, PK> or(final Criterion lhs, final Criterion rhs) {
        Criteria criteria = getCriteria();
        Criterion criterion = Restrictions.or(lhs, rhs);
        criteria.add(criterion);
        return this;
    }

    public BaseDao<T, PK> in(final String propertyName, final Object[] values) {
        Criteria criteria = getCriteria();
        if (values != null) {
            Criterion criterion = Restrictions.in(propertyName, values);
            criteria.add(criterion);
        }
        return this;
    }

    public BaseDao<T, PK> like(final String propertyName, final String value) {
        Criteria criteria = getCriteria();
        if (StringUtils.isNotEmpty(value)) {
            Criterion criterion = Restrictions.like(propertyName, value, MatchMode.ANYWHERE);
            criteria.add(criterion);
        }
        return this;
    }

    public BaseDao<T, PK> in(final String propertyName, final String[] value) {
        Criteria criteria = getCriteria();
        if (value.length > 1) {
            Criterion criterion = Restrictions.in(propertyName, value);
            criteria.add(criterion);
        }
        return this;
    }

    public BaseDao<T, PK> createAlias(final String associationPath, final String alias) {
        Criteria criteria = getCriteria();
        criteria.createAlias(associationPath, alias);
        return this;
    }

    public BaseDao<T, PK> addOrder(final Order order) {
        Criteria criteria = getCriteria();
        if (order != null) {
            criteria.addOrder(order);
        }
        return this;
    }

    // 1. criteriaThread.remove();
    public long count() {
        Criteria criteria = getCriteria();
        criteriaThread.remove();

        return countCriteriaResult(criteria);
    }

    // 2. criteriaThread.remove();
    public List<T> list() {
        Criteria criteria = getCriteria();
        criteriaThread.remove();

        @SuppressWarnings("unchecked")
        List<T> list = criteria.list();

        return list;
    }

    // 3. criteriaThread.remove();
    public Page<T> findPage(final Page<T> page) {
        Assert.notNull(page, "page cannot be null");
        Criteria c = getCriteria();
        criteriaThread.remove();

        if (page.isAutoCount()) {
            long totalCount = countCriteriaResult(c);
            page.setTotalCount(totalCount);
        }

        setPageParameterToCriteria(c, page);

        @SuppressWarnings("unchecked")
        List<T> result = c.list();
        page.setResult(result);
        return page;
    }

    // 4. criteriaThread.remove();
    public T unique() {
        Criteria criteria = getCriteria();
        criteriaThread.remove();

        @SuppressWarnings("unchecked")
        T t = (T) criteria.uniqueResult();

        return t;
    }
}
